-- Copyright (c) 2016-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is licensed under the BSD-style license found in the
-- LICENSE file in the root directory of this source tree.


{-# LANGUAGE GADTs #-}
{-# LANGUAGE NoRebindableSyntax #-}
{-# LANGUAGE OverloadedStrings #-}

module Duckling.Time.GA.Rules
  ( rules
  ) where

import Data.Text (Text)
import Prelude

import Duckling.Dimensions.Types
import Duckling.Duration.Helpers (isGrain)
import Duckling.Numeral.Helpers (parseInt)
import Duckling.Regex.Types
import Duckling.Time.Helpers
import Duckling.Types
import qualified Duckling.Ordinal.Types as TOrdinal
import qualified Duckling.TimeGrain.Types as TG

ruleArInn :: Rule
ruleArInn = Rule
  { name = "arú inné"
  , pattern =
    [ regex "ar(ú|u) inn(é|e)"
    ]
  , prod = \_ -> tt . cycleNth TG.Day $ - 2
  }

ruleNollaigNaMban :: Rule
ruleNollaigNaMban = Rule
  { name = "Nollaig na mBan"
  , pattern =
    [ regex "(l(á|a) |an )?nollaig (bheag|na mban)"
    ]
  , prod = \_ -> tt $ monthDay 1 6
  }

ruleInniu :: Rule
ruleInniu = Rule
  { name = "inniu"
  , pattern =
    [ regex "inniu"
    ]
  , prod = \_ -> tt today
  }

ruleAnOrdinalCycleINdiaidhTime :: Rule
ruleAnOrdinalCycleINdiaidhTime = Rule
  { name = "an <ordinal> <cycle> i ndiaidh <time>"
  , pattern =
    [ regex "an"
    , dimension Ordinal
    , dimension TimeGrain
    , regex "(i ndiaidh|tar (é|e)is)"
    , dimension Time
    ]
  , prod = \tokens -> case tokens of
      (_:Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
      _ -> Nothing
  }

ruleInn :: Rule
ruleInn = Rule
  { name = "inné"
  , pattern =
    [ regex "inn(é|e)"
    ]
  , prod = \_ -> tt . cycleNth TG.Day $ - 1
  }

ruleLFhileBrde :: Rule
ruleLFhileBrde = Rule
  { name = "Lá Fhéile Bríde"
  , pattern =
    [ regex "(l(á|a) )?(fh(e|é)ile|'?le) bh?r(í|i)de"
    ]
  , prod = \_ -> tt $ monthDay 2 1
  }

ruleLFhileVailintn :: Rule
ruleLFhileVailintn = Rule
  { name = "Lá Fhéile Vailintín"
  , pattern =
    [ regex "(l(á|a) )?(fh(e|é)ile|'?le) vailint(í|i)n"
    ]
  , prod = \_ -> tt $ monthDay 2 14
  }

ruleTimeSeo :: Rule
ruleTimeSeo = Rule
  { name = "<time> seo"
  , pattern =
    [ dimension Time
    , regex "seo"
    ]
  , prod = \tokens -> case tokens of
      (Token Time td:_) ->
        tt $ predNth 0 False td
      _ -> Nothing
  }

ruleTimeSeoChaite :: Rule
ruleTimeSeoChaite = Rule
  { name = "<time> seo chaite"
  , pattern =
    [ dimension Time
    , regex "seo ch?aite"
    ]
  , prod = \tokens -> case tokens of
      (Token Time td:_) ->
        tt $ predNth (-1) False td
      _ -> Nothing
  }

ruleTimeSeoChugainn :: Rule
ruleTimeSeoChugainn = Rule
  { name = "<time> seo chugainn"
  , pattern =
    [ Predicate isNotLatent
    , regex "seo (chugainn|at(a|á) ag teacht)"
    ]
  , prod = \tokens -> case tokens of
      (Token Time td:_) ->
        tt $ predNth 0 True td
      _ -> Nothing
  }

ruleAmrach :: Rule
ruleAmrach = Rule
  { name = "amárach"
  , pattern =
    [ regex "am(á|a)rach"
    ]
  , prod = \_ -> tt $ cycleNth TG.Day 1
  }

ruleYearLatent2 :: Rule
ruleYearLatent2 = Rule
  { name = "year (latent)"
  , pattern =
    [ Predicate $ isIntegerBetween 2101 10000
    ]
  , prod = \tokens -> case tokens of
      (token:_) -> do
        n <- getIntValue token
        tt . mkLatent $ year n
      _ -> Nothing
  }

ruleOrdinalRithe :: Rule
ruleOrdinalRithe = Rule
  { name = "<ordinal> ráithe"
  , pattern =
    [ dimension Ordinal
    , Predicate $ isGrain TG.Quarter
    ]
  , prod = \tokens -> case tokens of
      (token:_) -> do
        n <- getIntValue token
        tt . cycleNthAfter True TG.Quarter (n - 1) $
          cycleNth TG.Year 0
      _ -> Nothing
  }

ruleAnnaCycleRoimhTime :: Rule
ruleAnnaCycleRoimhTime = Rule
  { name = "(an|na) <cycle> roimh <time>"
  , pattern =
    [ regex "the"
    , dimension TimeGrain
    , regex "roimh"
    , dimension Time
    ]
  , prod = \tokens -> case tokens of
      (_:Token TimeGrain grain:_:Token Time td:_) ->
        tt $ cycleNthAfter False grain (-1) td
      _ -> Nothing
  }

ruleCycleShin :: Rule
ruleCycleShin = Rule
  { name = "<cycle> ó shin"
  , pattern =
    [ dimension TimeGrain
    , regex "(ó|o) shin"
    ]
  , prod = \tokens -> case tokens of
      (Token TimeGrain grain:_) ->
        tt . cycleNth grain $ 1
      _ -> Nothing
  }

ruleDdmm :: Rule
ruleDdmm = Rule
  { name = "dd/mm"
  , pattern =
    [ regex "(3[01]|[12]\\d|0?[1-9])/(0?[1-9]|1[0-2])"
    ]
  , prod = \tokens -> case tokens of
      (Token RegexMatch (GroupMatch (m1:m2:_)):_) -> do
        m <- parseInt m2
        d <- parseInt m1
        tt $ monthDay m d
      _ -> Nothing
  }

ruleIGceannCycle :: Rule
ruleIGceannCycle = Rule
  { name = "i gceann <cycle>"
  , pattern =
    [ regex "(i|faoi) g?ch?eann"
    , Predicate $ isIntegerBetween 1 9999
    , dimension TimeGrain
    ]
  , prod = \tokens -> case tokens of
      (_:Token Ordinal od:Token TimeGrain grain:_) ->
        tt $ cycleN True grain (TOrdinal.value od)
      _ -> Nothing
  }

ruleAnCycleDeTime :: Rule
ruleAnCycleDeTime = Rule
  { name = "an <cycle> de <time>"
  , pattern =
    [ regex "an"
    , dimension TimeGrain
    , regex "d[e']"
    , dimension Time
    ]
  , prod = \tokens -> case tokens of
      (_:Token TimeGrain grain:_:Token Time td:_) ->
        tt $ cycleNthAfter True grain 0 td
      _ -> Nothing
  }

ruleDayofmonthordinalNamedmonth :: Rule
ruleDayofmonthordinalNamedmonth = Rule
  { name = "<day-of-month>(ordinal) <named-month>"
  , pattern =
    [ Predicate isDOMOrdinal
    , Predicate isAMonth
    ]
  , prod = \tokens -> case tokens of
      (token:Token Time td:_) -> Token Time <$> intersectDOM td token
      _ -> Nothing
  }

ruleIntersectBy :: Rule
ruleIntersectBy = Rule
  { name = "intersect by \",\""
  , pattern =
    [ Predicate isNotLatent
    , regex ","
    , Predicate isNotLatent
    ]
  , prod = \tokens -> case tokens of
      (Token Time td1:_:Token Time td2:_) ->
        Token Time <$> intersect td1 td2
      _ -> Nothing
  }

ruleOrdinalRitheYear :: Rule
ruleOrdinalRitheYear = Rule
  { name = "<ordinal> ráithe <year>"
  , pattern =
    [ dimension Ordinal
    , Predicate $ isGrain TG.Quarter
    , dimension Time
    ]
  , prod = \tokens -> case tokens of
      (Token Ordinal od:_:Token Time td:_) ->
        tt $ cycleNthAfter False TG.Quarter (TOrdinal.value od - 1) td
      _ -> Nothing
  }

ruleCycleInniu :: Rule
ruleCycleInniu = Rule
  { name = "<cycle> ó inniu"
  , pattern =
    [ Predicate $ isIntegerBetween 1 9999
    , dimension TimeGrain
    , regex "(ó|o)(n l(á|a) (at(á|a) )?)?inniu"
    ]
  , prod = \tokens -> case tokens of
      (token:Token TimeGrain grain:_) -> do
        v <- getIntValue token
        tt $ cycleN True grain v
      _ -> Nothing
  }

ruleOrdinalCycleINdiaidhTime :: Rule
ruleOrdinalCycleINdiaidhTime = Rule
  { name = "<ordinal> <cycle> i ndiaidh <time>"
  , pattern =
    [ dimension Ordinal
    , dimension TimeGrain
    , regex "(i ndiaidh|tar (é|e)is)"
    , dimension Time
    ]
  , prod = \tokens -> case tokens of
      (Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
      _ -> Nothing
  }

ruleAnDayofmonthOrdinal :: Rule
ruleAnDayofmonthOrdinal = Rule
  { name = "an <day-of-month> (ordinal)"
  , pattern =
    [ regex "an|na"
    , Predicate isDOMOrdinal
    ]
  , prod = \tokens -> case tokens of
      (_:token:_) -> do
        n <- getIntValue token
        tt $ dayOfMonth n
      _ -> Nothing
  }

ruleIntersect :: Rule
ruleIntersect = Rule
  { name = "intersect"
  , pattern =
    [ Predicate isNotLatent
    , Predicate isNotLatent
    ]
  , prod = \tokens -> case tokens of
      (Token Time td1:Token Time td2:_) ->
        Token Time <$> intersect td1 td2
      _ -> Nothing
  }

ruleAnOrdinalCycleDeTime :: Rule
ruleAnOrdinalCycleDeTime = Rule
  { name = "an <ordinal> <cycle> de <time>"
  , pattern =
    [ regex "an"
    , dimension Ordinal
    , dimension TimeGrain
    , regex "d[e']"
    , dimension Time
    ]
  , prod = \tokens -> case tokens of
      (_:Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
      _ -> Nothing
  }

ruleLNaNaithreacha :: Rule
ruleLNaNaithreacha = Rule
  { name = "Lá na nAithreacha"
  , pattern =
    [ regex "l(á|a) na naithreacha"
    ]
  , prod = \_ -> tt $ nthDOWOfMonth 2 7 6
  }

ruleArAmrach :: Rule
ruleArAmrach = Rule
  { name = "arú amárach"
  , pattern =
    [ regex "ar(ú|u) am(á|a)rach"
    ]
  , prod = \_ -> tt $ cycleNth TG.Day 2
  }

ruleOrdinalCycleDeTime :: Rule
ruleOrdinalCycleDeTime = Rule
  { name = "<ordinal> <cycle> de <time>"
  , pattern =
    [ dimension Ordinal
    , dimension TimeGrain
    , regex "d[e']"
    , dimension Time
    ]
  , prod = \tokens -> case tokens of
      (Token Ordinal od:Token TimeGrain grain:_:Token Time td:_) ->
        tt $ cycleNthAfter True grain (TOrdinal.value od - 1) td
      _ -> Nothing
  }

ruleYyyymmdd :: Rule
ruleYyyymmdd = Rule
  { name = "yyyy-mm-dd"
  , pattern =
    [ regex "(\\d{2,4})-(0?[1-9]|1[0-2])-(3[01]|[12]\\d|0?[1-9])"
    ]
  , prod = \tokens -> case tokens of
      (Token RegexMatch (GroupMatch (m1:m2:m3:_)):_) -> do
        y <- parseInt m1
        m <- parseInt m2
        d <- parseInt m3
        tt $ yearMonthDay y m d
      _ -> Nothing
  }

ruleArDate :: Rule
ruleArDate = Rule
  { name = "ar <date>"
  , pattern =
    [ regex "ar"
    , dimension Time
    ]
  , prod = \tokens -> case tokens of
      (_:x:_) -> Just x
      _ -> Nothing
  }

ruleAnNollaig :: Rule
ruleAnNollaig = Rule
  { name = "An Nollaig"
  , pattern =
    [ regex "(l(á|a) |an )?(nollai?g)"
    ]
  , prod = \_ -> tt $ monthDay 12 25
  }

ruleOnANamedday :: Rule
ruleOnANamedday = Rule
  { name = "on a named-day"
  , pattern =
    [ regex "ar an"
    , Predicate isADayOfWeek
    ]
  , prod = \tokens -> case tokens of
      (_:x:_) -> Just x
      _ -> Nothing
  }

ruleYearLatent :: Rule
ruleYearLatent = Rule
  { name = "year (latent)"
  , pattern =
    [ Predicate $ isIntegerBetween 1 999
    ]
  , prod = \tokens -> case tokens of
      (token:_) -> do
        n <- getIntValue token
        tt . mkLatent $ year n
      _ -> Nothing
  }

ruleAnois :: Rule
ruleAnois = Rule
  { name = "anois"
  , pattern =
    [ regex "anois|(ag an (t-?)?am seo)"
    ]
  , prod = \_ -> tt now
  }

ruleLFhilePdraig :: Rule
ruleLFhilePdraig = Rule
  { name = "Lá Fhéile Pádraig"
  , pattern =
    [ regex "(l(á|a) )?(fh(e|é)ile|'?le) ph?(á|a)draig"
    ]
  , prod = \_ -> tt $ monthDay 3 17
  }

ruleAnCycleINdiaidhTime :: Rule
ruleAnCycleINdiaidhTime = Rule
  { name = "an <cycle> i ndiaidh <time>"
  , pattern =
    [ regex "the"
    , dimension TimeGrain
    , regex "(i ndiaidh|tar (é|e)is)"
    , dimension Time
    ]
  , prod = \tokens -> case tokens of
      (_:Token TimeGrain grain:_:Token Time td:_) ->
        tt $ cycleNthAfter False grain 1 td
      _ -> Nothing
  }

ruleDayofmonthOrdinal :: Rule
ruleDayofmonthOrdinal = Rule
  { name = "<day-of-month> (ordinal)"
  , pattern =
    [ Predicate isDOMOrdinal
    ]
  , prod = \tokens -> case tokens of
      (token:_) -> do
        n <- getIntValue token
        tt . mkLatent $ dayOfMonth n
      _ -> Nothing
  }

ruleAnCycleSeo :: Rule
ruleAnCycleSeo = Rule
  { name = "an <cycle> seo"
  , pattern =
    [ regex "an"
    , dimension TimeGrain
    , regex "seo"
    ]
  , prod = \tokens -> case tokens of
      (_:Token TimeGrain grain:_) ->
        tt $ cycleNth grain 0
      _ -> Nothing
  }

ruleAnDayofmonthNonOrdinal :: Rule
ruleAnDayofmonthNonOrdinal = Rule
  { name = "an <day-of-month> (non ordinal)"
  , pattern =
    [ regex "an|na"
    , Predicate isDOMInteger
    ]
  , prod = \tokens -> case tokens of
      (_:token:_) -> do
        v <- getIntValue token
        tt . mkLatent $ dayOfMonth v
      _ -> Nothing
  }

ruleDNamedday :: Rule
ruleDNamedday = Rule
  { name = "dé named-day"
  , pattern =
    [ regex "d(é|e)"
    , Predicate isADayOfWeek
    ]
  , prod = \tokens -> case tokens of
      (_:x:_) -> Just x
      _ -> Nothing
  }

ruleYear :: Rule
ruleYear = Rule
  { name = "year"
  , pattern =
    [ Predicate $ isIntegerBetween 1000 2100
    ]
  , prod = \tokens -> case tokens of
      (token:_) -> do
        v <- getIntValue token
        tt $ year v
      _ -> Nothing
  }

ruleCycleRoimhTime :: Rule
ruleCycleRoimhTime = Rule
  { name = "<cycle> roimh <time>"
  , pattern =
    [ dimension TimeGrain
    , regex "roimhe?"
    , dimension Time
    ]
  , prod = \tokens -> case tokens of
      (Token TimeGrain grain:_:Token Time td:_) ->
        tt $ cycleNthAfter False grain (-1) td
      _ -> Nothing
  }

ruleAbsorptionOfAfterNamedDay :: Rule
ruleAbsorptionOfAfterNamedDay = Rule
  { name = "absorption of , after named day"
  , pattern =
    [ Predicate isADayOfWeek
    , regex ","
    ]
  , prod = \tokens -> case tokens of
      (x:_) -> Just x
      _ -> Nothing
  }

ruleDdmmyyyy :: Rule
ruleDdmmyyyy = Rule
  { name = "dd/mm/yyyy"
  , pattern =
    [ regex "(3[01]|[12]\\d|0?[1-9])[-/](0?[1-9]|1[0-2])[/-](\\d{2,4})"
    ]
  , prod = \tokens -> case tokens of
      (Token RegexMatch (GroupMatch (m1:m2:m3:_)):_) -> do
        y <- parseInt m3
        m <- parseInt m2
        d <- parseInt m1
        tt $ yearMonthDay y m d
      _ -> Nothing
  }

ruleAnNamedday :: Rule
ruleAnNamedday = Rule
  { name = "an named-day"
  , pattern =
    [ regex "an"
    , Predicate isADayOfWeek
    ]
  , prod = \tokens -> case tokens of
      (_:x:_) -> Just x
      _ -> Nothing
  }

ruleDaysOfWeek :: [Rule]
ruleDaysOfWeek = mkRuleDaysOfWeek
  [ ( "Monday"   , "luai?n|lu\\.?"             )
  , ( "Tuesday"  , "mh?(á|a)irt|m(á|a)?\\.?"   )
  , ( "Wednesday", "ch?(é|e)adaoin|c(é|e)\\.?" )
  , ( "Thursday" , "d(é|e)ardaoin|d(é|e)?\\.?" )
  , ( "Friday"   , "h?aoine|ao\\.?"            )
  , ( "Saturday" , "sathai?rn|sa\\.?"          )
  , ( "Sunday"   , "domhnai?[cg]h|do\\.?"      )
  ]

ruleMonths :: [Rule]
ruleMonths = mkRuleMonths
  [ ( "January"  , "(mh?(í|i) )?(an )?t?ean(á|a)ir|ean\\.?"     )
  , ( "February" , "(mh?(í|i) )?(na )?feabhra|fea\\.?"          )
  , ( "March"    , "(mh?(í|i) )?(an )?mh?(á|a)rta|m(á|a)r\\.?"  )
  , ( "April"    , "(mh?(í|i) )?(an )?t?aibre(á|a)i?n|abr\\.?"  )
  , ( "May"      , "(mh?(í|i) )?(na )?bh?ealtaine|bea\\.?"      )
  , ( "June"     , "(mh?(í|i) )?(an )?mh?eith(ea|i)mh|mei\\.?"  )
  , ( "July"     , "(mh?(í|i) )?i(ú|u)il|i(ú|u)i\\.?"           )
  , ( "August"   , "(mh?(í|i) )?(na )?l(ú|u)nasa|l(ú|u)n\\.?"   )
  , ( "September", "(mh?(í|i) )?mh?e(á|a)n f(ó|o)mhair|mef?\\.?")
  , ( "October"  , "(mh?(í|i) )?(na )?nollai?g|nol\\.?"         )
  , ( "November" , "(mh?(í|i) )?(na )?samh(ain|na)|sam\\.?"     )
  , ( "December" , "(mh?(í|i) )?(na )?nollai?g|nol\\.?"         )
  ]

ruleCycleINdiaidhTime :: Rule
ruleCycleINdiaidhTime = Rule
  { name = "<cycle> i ndiaidh <time>"
  , pattern =
    [ dimension TimeGrain
    , regex "(i ndiaidh|tar (é|e)is)"
    , dimension Time
    ]
  , prod = \tokens -> case tokens of
      (Token TimeGrain grain:_:Token Time td:_) ->
        tt $ cycleNthAfter False grain 1 td
      _ -> Nothing
  }

ruleDayofmonthordinalNamedmonthYear :: Rule
ruleDayofmonthordinalNamedmonthYear = Rule
  { name = "<day-of-month>(ordinal) <named-month> year"
  , pattern =
    [ Predicate isDOMOrdinal
    , Predicate isAMonth
    , regex "(\\d{2,4})"
    ]
  , prod = \tokens -> case tokens of
      (token:Token Time td:Token RegexMatch (GroupMatch (match:_)):_) -> do
        intVal <- parseInt match
        dom <- intersectDOM td token
        Token Time <$> intersect dom (year intVal)
      _ -> Nothing
  }

rules :: [Rule]
rules =
  [ ruleAbsorptionOfAfterNamedDay
  , ruleAmrach
  , ruleAnCycleDeTime
  , ruleAnCycleINdiaidhTime
  , ruleAnCycleSeo
  , ruleAnDayofmonthNonOrdinal
  , ruleAnDayofmonthOrdinal
  , ruleAnNamedday
  , ruleAnNollaig
  , ruleAnOrdinalCycleDeTime
  , ruleAnOrdinalCycleINdiaidhTime
  , ruleAnnaCycleRoimhTime
  , ruleAnois
  , ruleArAmrach
  , ruleArDate
  , ruleArInn
  , ruleCycleINdiaidhTime
  , ruleCycleInniu
  , ruleCycleRoimhTime
  , ruleCycleShin
  , ruleDNamedday
  , ruleDayofmonthOrdinal
  , ruleDayofmonthordinalNamedmonth
  , ruleDayofmonthordinalNamedmonthYear
  , ruleDdmm
  , ruleDdmmyyyy
  , ruleIGceannCycle
  , ruleInn
  , ruleInniu
  , ruleIntersect
  , ruleIntersectBy
  , ruleLFhileBrde
  , ruleLFhilePdraig
  , ruleLFhileVailintn
  , ruleLNaNaithreacha
  , ruleNollaigNaMban
  , ruleOnANamedday
  , ruleOrdinalCycleDeTime
  , ruleOrdinalCycleINdiaidhTime
  , ruleOrdinalRithe
  , ruleOrdinalRitheYear
  , ruleTimeSeo
  , ruleTimeSeoChaite
  , ruleTimeSeoChugainn
  , ruleYear
  , ruleYearLatent
  , ruleYearLatent2
  , ruleYyyymmdd
  ]
  ++ ruleDaysOfWeek
  ++ ruleMonths
